﻿Imports System.ComponentModel
Public Class CodeEditBox
    Inherits RichTextBox
    Const WM_PASTE = &H302
    Private Enum EnumChangeColor
        EnumKeyWordColor = 0
        EnumClassTypeColor = 1
        EnumErrorColor = 2
        EnumDescriptionColor = 3
        EnumStringTypeColor = 4
    End Enum
    Public Enum EnumCodeMode
        VBScript
        JavaScript
    End Enum
    Private _KeyWordColor As Color = Color.FromArgb(0, 0, 255)
    Private _ClassTypeColor As Color = Color.FromArgb(43, 145, 175)
    Private _ErrorColor As Color = Color.Red
    Private _DescriptionColor As Color = Color.FromArgb(0, 128, 0)
    Private _StringTypeColor As Color = Color.FromArgb(163, 21, 21)
    Private _TabCount As Integer = 4
    Private VbsKeyWords As New VbsKeyCode
    Private _CodeMode As EnumCodeMode
    Public Sub New()
        MyBase.WordWrap = False
        MyBase.ScrollBars = RichTextBoxScrollBars.Both
        MyBase.AcceptsTab = True
        LoadForamtData()
    End Sub
    <Browsable(False)> _
    <EditorBrowsable(EditorBrowsableState.Never)> _
    Public Overloads Property WordWrap() As Boolean
        Get
            Return MyBase.WordWrap
        End Get
        Set(ByVal value As Boolean)
            MyBase.WordWrap = False
        End Set
    End Property
    Public Property CodeMode As EnumCodeMode
        Get
            Return _CodeMode
        End Get
        Set(value As EnumCodeMode)
            _CodeMode = value
            LoadForamtData()
            FreshString()
        End Set
    End Property
    Private Sub LoadForamtData()
        With VbsKeyWords
            If .FunCode IsNot Nothing Then .FunCode.Clear()
            If .SplitWord IsNot Nothing Then .SplitWord.Clear()
            If _CodeMode = EnumCodeMode.VBScript Then
                .AddSplitWord("Function", "End Function")
                .AddSplitWord("Private Function", "End Function")
                .AddSplitWord("Public Function", "End Function")
                .AddSplitWord("Sub", "End Sub")
                .AddSplitWord("Private Sub", "End Sub")
                .AddSplitWord("Public Sub", "End Sub")
                .AddSplitWord("With", "End With")
                .AddSplitWord("Do", "Loop")
                .AddSplitWord("While", "Wend")
                .AddSplitWord("If", "Then", "End If")
                ' .AddSplitWord("For", "Next")
                .AddSplitWord("For Each", "In", "Next")
                .AddSplitWord("For Each", "As", "In", "Next")
                .AddSplitWord("For", "To", "Step", "Next")
                .AddSplitWord("For", "To", "Next")
                '日期时间函数
                .AddFunCode("CDate,Date,DateAdd,DateDiff,DatePart,DateSerial,DateValue,Day,FormatDateTime,Hour,IsDate,Minute,Month,MonthName,Now,Second,Time,Timer,TimeSerial,TimeValue,Weekday,WeekdayName,Year")
                'Conversion函数
                .AddFunCode("Asc,CBool,CByte,CCur,CDate,CDbl,Chr,CInt,CLng,CSng,CStr,Hex,Oct,Val")
                'Foramt函数
                .AddFunCode("FormatCurrency,FormatDateTime,FormatNumber,FormatPercent")
                'Math函数
                .AddFunCode("Abs,Atn,Cos,Exp,Hex,Int,Fix,Log,Oct,Rnd,Sgn,Sin,Sqr,Tan")
                'Array函数
                .AddFunCode("Array,Filter,IsArray,Join,Lbound,Split,UBound,Mod")
                'String函数
                .AddFunCode("InStr,InStrRev,LCase,Left,Len,LTrim,Trim,Mid,Replace,Right,Space,StrComp,String,StrReverse,UCase")
                '其他函数
                .AddFunCode("Dim,CreateObject,Eval,GetLocale,GetObject,GetRef,InputBox,IsEmpty,IsNull,IsNumeric,IsObject,LoadPicture,MsgBox,RGB,Round,ScriptEngine,ScriptEngineBuildVersion,ScriptEngineMajorVersion,ScriptEngineMinorVersion,SetLocale,TypeName,VarType,Call,Nothing,Null")
            Else
                .AddFunCode("function,private,public,var,alert,document.getElementById,function,write,return,if,else,switch,case,default,Date,getDay")
                .AddFunCode("for,length,while,break,try,catch,throw,adddlert,with,MAX VALUE,MIN VALUE,NEGATIVE INFINITIVE,POSITIVE INFINITIVE,NaN,prototype,constructor,toExponential,toFixed,toPrecision,toString,valueOf")
                .AddFunCode("toUpperCase,getTime,setFullYear,toUTCString,In,concat,join,sort,Boolean,round,random,max,min,RegExp,exec,compile,window,innerHeight,innerWidth,clientWidth,clientHeight,body,open,close,moveTo,resizeTo")
                .AddFunCode("screen,availWidth,availHeight,location,hostname,pathname,port,portocol,href,assign,history,back,forward,navigator,confirm,prompt,setTimeout,clearTimeout,indexof,lastof,cookie")
            End If
        End With
    End Sub
    Private Sub CheckVbsLineCode(ByVal RowIndex As Integer, Optional ByVal OldSelectionStart As Long = -1, Optional ByVal KeyCode As Integer = 0)
        On Error Resume Next
        Dim CheckString As String = Me.Lines(RowIndex)
        Dim DIndex As Integer = CheckString.IndexOf("'")
        Dim CheckCode As String = FormatSplitWordOnOneSpace(CheckString)
        Dim FirstIndex As Long = Me.GetFirstCharIndexFromLine(RowIndex)
        Dim TabC As Integer = 0, HasKeyWord As Boolean = False
        If String.IsNullOrEmpty(CheckCode) Then Return
        If _CodeMode = EnumCodeMode.JavaScript Then DIndex = CheckString.IndexOf("//")
        DisablePaint(Me.Handle)
        If DIndex >= 0 Then
            Me.SelectionStart = Me.GetFirstCharIndexFromLine(RowIndex) + DIndex
            Me.SelectionLength = CheckString.Length - DIndex + 1
            ChangeKeyWordColor(EnumChangeColor.EnumDescriptionColor)
            Me.SelectionStart = OldSelectionStart
            Me.SelectionLength = 0
            Return
        End If
        With VbsKeyWords.SplitWord

            For i = 0 To .Count - 1
                Dim SearchString As String = .Item(i).StartWord1.ToLower
                Dim Tag As Object = VbsKeyWords.SplitWordTag
                Dim CanCheck As Boolean = False, PIndex As Integer = -1
                If Tag IsNot Nothing Then If Val(Tag) <> i And Val(Tag) >= 0 Then CanCheck = True
                If Tag Is Nothing Then CanCheck = True
                If CanCheck Then
                    Select Case .Item(i).WordMode
                        Case EnumSplitMode.NoneSplit
                            PIndex = CheckCode.ToLower.IndexOf(SearchString)
                            ' MsgBox(PIndex)
                            If PIndex = 0 Then
                                Dim tmpIndex As Integer = CheckString.ToLower.IndexOf(SearchString)
                                Me.SelectionStart = FirstIndex + tmpIndex
                                Me.SelectionLength = CheckString.Length - tmpIndex
                                Me.SelectedText = CheckCode.Replace(CheckCode.Substring(0, SearchString.Length), .Item(i).StartWord1)
                                Me.SelectionStart = FirstIndex + tmpIndex
                                Me.SelectionLength = SearchString.Length
                                ChangeKeyWordColor(EnumChangeColor.EnumKeyWordColor, OldSelectionStart)
                                VbsKeyWords.SplitWordTag = i
                                HasKeyWord = True

                            Else
                                SearchString = .Item(i).EndWord.ToLower
                                PIndex = CheckCode.ToLower.IndexOf(SearchString)
                                If PIndex = 0 Then
                                    Dim tmpIndex As Integer = CheckString.ToLower.IndexOf(SearchString)
                                    Me.SelectionStart = FirstIndex + tmpIndex
                                    Me.SelectionLength = CheckString.Length - tmpIndex
                                    Me.SelectedText = CheckCode.Replace(CheckCode.Substring(0, SearchString.Length), .Item(i).EndWord)
                                    Me.SelectionStart = FirstIndex + tmpIndex
                                    Me.SelectionLength = SearchString.Length
                                    ChangeKeyWordColor(EnumChangeColor.EnumKeyWordColor, OldSelectionStart)
                                    VbsKeyWords.SplitWordTag = i
                                    HasKeyWord = True
                                    ' Return
                                End If
                            End If
                        Case EnumSplitMode.SplitMode
                            Dim SearchString1 As String = .Item(i).StartWord1.ToLower, SearchString2 As String = .Item(i).StartWord2.ToLower
                            Dim Pindex1 As Integer = CheckCode.IndexOf(SearchString1), Pindex2 As Integer
                            If Pindex1 = 0 Then
                                Dim LastString As String = CheckCode.Substring(Pindex1 + SearchString1.Length)
                                Pindex2 = LastString.IndexOf(SearchString2)
                                If Pindex2 > 0 Then
                                    Dim WriteString As String, tmpC As Integer = getFrontSpaceCount(CheckString)
                                    WriteString = CheckCode.Replace(CheckCode.Substring(0, SearchString1.Length), .Item(i).StartWord1).Replace(CheckCode.Substring(Pindex2 + SearchString1.Length, SearchString2.Length), .Item(i).StartWord2)
                                    TabC = getFrontLineSpaceCount(RowIndex)
                                    If TabC < 0 And tmpC <= 0 Then
                                        TabC = 4
                                    Else
                                        TabC = IIf(tmpC < 0, 4, tmpC)
                                    End If
                                    Me.SelectionStart = FirstIndex
                                    Me.SelectionLength = CheckString.Length
                                    Me.SelectedText = Space(TabC) + WriteString
                                    Me.SelectionStart = FirstIndex + TabC
                                    Me.SelectionLength = SearchString1.Length
                                    ChangeKeyWordColor(EnumChangeColor.EnumKeyWordColor)
                                    Me.SelectionStart = FirstIndex + TabC + Pindex2 + SearchString1.Length
                                    Me.SelectionLength = SearchString2.Length
                                    ChangeKeyWordColor(EnumChangeColor.EnumKeyWordColor)
                                    HasKeyWord = True
                                Else : GoTo CheckSplitEndWord
                                End If
                            Else
CheckSplitEndWord:
                                If CheckCode.ToLower = .Item(i).EndWord.ToLower Then
                                    TabC = getFrontLineSpaceCount(RowIndex, .Item(i).StartWord1, .Item(i).EndWord)
                                    If TabC < 0 Then TabC = 0
                                    Me.SelectionStart = FirstIndex
                                    Me.SelectionLength = CheckString.Length
                                    Me.SelectedText = Space(TabC) & .Item(i).EndWord
                                    Me.SelectionStart = FirstIndex
                                    Me.SelectionLength = Me.Lines(RowIndex).Length
                                    ChangeKeyWordColor(EnumChangeColor.EnumKeyWordColor)
                                    HasKeyWord = True
                                End If
                            End If
                        Case EnumSplitMode.ThreeSplitMode

                    End Select
                End If
            Next
        End With
        If KeyCode = 13 Then
            Me.SelectionStart = Me.GetFirstCharIndexFromLine(RowIndex + 1) : Me.SelectionLength = 0
            'Me.SelectedText = Space(TabC)
            '  Me.Parent.Text = KeyCode & RowIndex
            'Else
            '    Me.SelectionStart = OldSelectionStart
            If HasKeyWord = False Then
                Dim tabcc As Integer = getFrontLineSpaceCount(RowIndex)
                If tabcc < 0 Then tabcc = 0
                Me.SelectedText = Space(tabcc)
            End If
        End If
        AllowPaint(Me.Handle) : Me.Refresh()
    End Sub
    Private Sub CheckInputString(ByVal RowIndex As Integer, ByVal OldSelectionStart As Long)
        If RowIndex < 0 Or RowIndex >= Me.Lines.Count Then Return
        Dim tmp As String = Me.Lines(RowIndex)
        Dim tmpStr1 As String = tmp
        Dim A As Long, B As Long, FirstIndex As Long = Me.GetFirstCharIndexFromLine(RowIndex)
        Dim SIndex As Long = tmp.IndexOf("'")
        A = tmp.IndexOf(Chr(34))
        tmpStr1 = tmp.Substring(A + 1)
        DisablePaint(Me.Handle)
        Do Until A < 0
            Try
                If A >= SIndex And SIndex > 0 Then Exit Do
                If A + 1 > tmp.Length Then Exit Do
                tmpStr1 = tmp.Substring(A + 1)
                B = tmpStr1.IndexOf(Chr(34))
                If B >= 0 Then
                    Me.SelectionStart = A + FirstIndex
                    Me.SelectionLength = B + 2
                    ChangeKeyWordColor(EnumChangeColor.EnumStringTypeColor)
                    A = A + tmpStr1.Substring(B + 1).IndexOf(Chr(34)) + 2 + B
                Else
                    Exit Do
                End If
            Catch
                Exit Do
            End Try
        Loop
        If SIndex > 0 Then Me.SelectionStart = FirstIndex + SIndex : Me.SelectionLength = tmp.Length - SIndex : ChangeKeyWordColor(EnumChangeColor.EnumDescriptionColor)
        Me.SelectionStart = OldSelectionStart
        AllowPaint(Me.Handle)
        Me.Refresh()
    End Sub
    Private Function FormatSplitWordOnOneSpace(ByVal Value As String) As String
        On Error Resume Next
        Dim a() As String = Value.Split(" ")
        Dim Result As String = vbNullString
        For i = 0 To a.Length - 1
            If Not (String.IsNullOrEmpty(a(i))) Then
                If Not String.IsNullOrEmpty(Result) Then
                    Result += " " & a(i)
                Else
                    Result = a(i)
                End If
            End If
        Next
        Return Result
    End Function
    Private Sub ChangeKeyWordColor(ByVal ColorMode As EnumChangeColor, Optional ByVal OldStartIndex As Long = -1)
        Select Case ColorMode
            Case EnumChangeColor.EnumKeyWordColor
                Me.SelectionColor = _KeyWordColor
            Case EnumChangeColor.EnumClassTypeColor
                Me.SelectionColor = _ClassTypeColor
            Case EnumChangeColor.EnumDescriptionColor
                Me.SelectionColor = _DescriptionColor
            Case EnumChangeColor.EnumErrorColor
                Me.SelectionColor = _ErrorColor
            Case EnumChangeColor.EnumStringTypeColor
                Me.SelectionColor = _StringTypeColor
        End Select
        If OldStartIndex < 0 Then
            Dim SelectedStartIndex As Long = Me.SelectionStart + Me.SelectionLength
            Me.SelectionStart = SelectedStartIndex
        Else
            Me.SelectionStart = OldStartIndex
        End If
        Me.SelectionColor = Me.ForeColor
        Me.SelectionFont = Me.Font
    End Sub
    Private Sub CheckFunName(ByVal RowIndex As Long, ByVal OldSelectionStart As Long)
        If RowIndex < 0 Or RowIndex > Me.Lines.Count - 1 Then Return
        Dim tmp As String = Me.Lines(RowIndex)
        Dim FirstIndex As Long = Me.GetFirstCharIndexFromLine(RowIndex)
        Dim CheckCode As New List(Of IsInCheckCode)
        CheckCode.Add(New IsInCheckCode(48, 57))
        CheckCode.Add(New IsInCheckCode(65, 90))
        CheckCode.Add(New IsInCheckCode(97, 122))
        With VbsKeyWords.FunCode
            DisablePaint(Me.Handle)
            Try
                For i = 0 To .Count - 1 Step 1
                    Dim tmpSearch As String = .Item(i).FunName.ToLower
                    Dim Vindex As Integer = tmp.ToLower.IndexOf(tmpSearch)
                    Do Until Vindex < 0 Or Vindex > tmp.Length - tmpSearch.Length
                        If Vindex = 0 Then
                            Me.SelectionStart = FirstIndex + Vindex
                            Me.SelectionLength = .Item(i).FunName.Length
                            ChangeKeyWordColor(EnumChangeColor.EnumKeyWordColor)
                            Me.SelectionStart = FirstIndex + Vindex
                            Me.SelectionLength = .Item(i).FunName.Length
                            Me.SelectedText = .Item(i).FunName
                        ElseIf Vindex > 0 And Vindex + tmpSearch.Length < tmp.Length Then
                            Dim FrontCode As Integer = Asc(tmp.Substring(Vindex - 1, 1))
                            Dim LastCode As Integer = Asc(tmp.Substring(tmpSearch.Length + Vindex, 1))
                            If Not (IsExistsCode(FrontCode, CheckCode)) And Not (IsExistsCode(LastCode, CheckCode)) Then
                                Me.SelectionStart = FirstIndex + Vindex
                                Me.SelectionLength = .Item(i).FunName.Length
                                ChangeKeyWordColor(EnumChangeColor.EnumKeyWordColor)
                                Me.SelectionStart = FirstIndex + Vindex
                                Me.SelectionLength = .Item(i).FunName.Length
                                Me.SelectedText = .Item(i).FunName
                            End If
                        End If
                        If Vindex + tmpSearch.Length + 1 < tmp.Length Then
                            Dim tmpValue As String = tmp.Substring(Vindex + tmpSearch.Length + 1)
                            Dim s As Integer = tmpValue.ToLower.IndexOf(tmpSearch)
                            Vindex = s + Vindex + 1
                            If s < 0 Then Exit Do
                        Else
                            Exit Do
                        End If
                    Loop
                Next
            Catch
            End Try
            Me.SelectionStart = OldSelectionStart
        End With
        AllowPaint(Me.Handle)
        Me.Refresh()
    End Sub
    ''' <summary>
    ''' 获取当前行前一行最前面空格数量
    ''' </summary>
    ''' <param name="Index"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function getFrontLineSpaceCount(ByVal Index As Integer, Optional ByVal KeyWord As String = vbNullString, Optional ByVal EndW As String = vbNullString) As Integer
        If Index - 1 <= 0 Or Index - 1 > Me.Lines.Count - 1 Then Return -1
        Dim WordCount1 As Integer = 0, WordCount2 As Integer = 0
        For i = Index - 1 To 0 Step -1
            If String.IsNullOrEmpty(KeyWord) Then
                Dim Tmp As String = Me.Lines(i)
                Dim Result As Integer = getFrontSpaceCount(Tmp)
                If Result >= 0 Then Return Result
            Else
                With VbsKeyWords.SplitWord
                    Dim Tmp As String = Me.Lines(i)
                    If Tmp.Trim.ToLower = EndW.Trim.ToLower Then WordCount1 += 1
                    If isExistsInKeyWord(Tmp, KeyWord) Then
                        If WordCount1 = WordCount2 Then
                            Dim Result As Integer = getFrontSpaceCount(Tmp)
                            Return Result
                        End If
                        WordCount2 += 1
                    End If
                End With
            End If
        Next
        Return -1
    End Function
    ''' <summary>
    ''' 获取字符串左边空格数量
    ''' </summary>
    ''' <param name="Value"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function getFrontSpaceCount(ByVal Value As String) As Integer
        Dim tmp As String = Value
        If Not (String.IsNullOrEmpty(Tmp)) Then
            Dim Result As Integer = 0
            For j = 0 To Tmp.Length - 1
                If Asc(tmp.Substring(j, 1)) = 32 Then
                    Result += 1
                Else
                    Return Result
                End If
            Next
            Return Result
        Else
            Return -1
        End If
    End Function
    ''' <summary>
    ''' 判断字符串中是否包含自定的关键词
    ''' </summary>
    ''' <param name="Value"></param>
    ''' <param name="KeyWord"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function isExistsInKeyWord(ByVal Value As String, ByVal KeyWord As String) As Boolean
        Dim checkString As String = FormatSplitWordOnOneSpace(Value)
        KeyWord = KeyWord.Trim()
        If (" " & checkString.ToLower & " ").IndexOf(" " & KeyWord.ToLower & " ") >= 0 Then
            Return True
        Else
            Return False
        End If
    End Function
    Private Sub CodeEditBox_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.DoubleClick
        Dim OldSelectStart As Long = Me.SelectionStart
        Dim Index As Integer
        For i = 0 To Me.Lines.Count - 1
            CheckVbsLineCode(i)
            Index = Me.GetFirstCharIndexFromLine(i) + Me.Lines(i).Length
            CheckInputString(i, Index)
            CheckFunName(i, Index)
        Next
    End Sub

    Private Sub CodeEditBox_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
        If e.KeyCode = 9 Then Me.SelectedText = Space(_TabCount) : e.Handled = True
    End Sub

    Private Sub CodeEditBox_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles Me.KeyPress
        If Asc(e.KeyChar) = 9 Then e.Handled = True
    End Sub

    Private Sub CodeEditBox_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp
        If e.KeyCode = 13 Then CheckVbsLineCode(Me.GetLineFromCharIndex(Me.SelectionStart) - 1, Me.SelectionStart, e.KeyCode)
    End Sub

    Private Sub CodeEditBox_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.TextChanged
        Dim AppendString As String
        Try
            'a = GetKeyboardLayout(0)
            If Me.SelectionLength > 0 Then
                AppendString = Me.SelectedText
            Else
                AppendString = Me.Text.Substring(Me.SelectionStart, 1)
            End If
            If Asc(AppendString) > 0 Then
                Dim LineIndex As Integer = Me.GetLineFromCharIndex(Me.SelectionStart)
                Dim OldIndex As Long = Me.SelectionStart
                CheckInputString(LineIndex, OldIndex)
                CheckFunName(LineIndex, OldIndex)
            End If
        Catch
        End Try
    End Sub
    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
        If m.Msg = WM_PASTE Then
            For i = 0 To Me.Lines.Count - 1
                CheckVbsLineCode(i)
            Next
        End If
        MyBase.WndProc(m)
    End Sub
    Public Sub FreshString()
        Me.SelectionColor = Me.ForeColor
        Dim Index As Integer
        For i = 0 To Me.Lines.Count - 1
            CheckVbsLineCode(i)
            Index = Me.GetFirstCharIndexFromLine(i) + Me.Lines(i).Length
            CheckInputString(i, Index)
            CheckFunName(i, Index)
        Next
    End Sub
End Class
